package com.lianqi.emcpframework.core.bean;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.util.ClassUtils;

import com.lianqi.emcpframework.core.dict.EnumDict;
import com.lianqi.emcpframework.core.proxy.Proxy;
import com.lianqi.emcpframework.utils.DateTimeUtils;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @program emcp-framework
 * @ClassName FastBeanCopier
 * @description: 快速bean属性拷贝
 * @author: sky
 * @create: 2018/11/16 22:32
 */
@Slf4j
public class FastBeanCopier {

    private static final Map<CacheKey, Copier> CACHE = new HashMap<>();

    private static final PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();

    public static final Map<Class, Class> wrapperMapper = new HashMap<>();

    private static BeanCreator BEAN_CREATOR;

    public static final TypeConverter TYPE_CONVERTER;

    private static final String PACKAGE_START_STR = "package ";

    public static void setBeanCreator(BeanCreator creator) {
        BEAN_CREATOR = creator;
    }

    public static BeanCreator getBeanCreator() {
        return BEAN_CREATOR;
    }

    static {
        wrapperMapper.put(byte.class, Byte.class);
        wrapperMapper.put(short.class, Short.class);
        wrapperMapper.put(int.class, Integer.class);
        wrapperMapper.put(float.class, Float.class);
        wrapperMapper.put(double.class, Double.class);
        wrapperMapper.put(char.class, Character.class);
        wrapperMapper.put(boolean.class, Boolean.class);
        wrapperMapper.put(long.class, Long.class);
        BEAN_CREATOR = new BeanCreator() {
            @Override
            @SneakyThrows
            public <T> T newInstance(Class<T> beanType) {
                return beanType == Map.class ? (T)new HashMap<>() : beanType.newInstance();
            }
        };
        TYPE_CONVERTER = new DefaultTypeConverter();
    }

    public static Set<String> include(String... properties) {
        return new HashSet<String>(Arrays.asList(properties)) {
            @Override
            public boolean contains(Object o) {
                return !super.contains(o);
            }
        };
    }

    public static <T, S> T copy(S source, T target, String... ignore) {
        return copy(source, target, TYPE_CONVERTER, ignore);
    }

    private static <T, S> T copy(S source, T target, TypeConverter typeConverter, String... ignore) {
        return copy(source, target, typeConverter,
            (ignore == null || ignore.length == 0) ? new HashSet<>() : new HashSet<>(Arrays.asList(ignore)));
    }

    public static <T, S> T copy(S source, T target, TypeConverter typeConverter, Set<String> ignore) {
        if (source instanceof Map && target instanceof Map) {
            ((Map)target).putAll((Map)source);
            return target;
        }

        getCopier(source, target, true).copy(source, target, ignore, typeConverter);
        return target;
    }

    /**
     * 获取属性拷贝代理类
     * 
     * @param source
     *            源对象
     * @param target
     *            目标对象
     * @param autoCreate
     *            是否自动创建
     * @param <S>
     *            源对象泛型
     * @param <T>
     *            目标对象泛型
     * @return 代理类
     */
    private static <S, T> Copier getCopier(S source, T target, boolean autoCreate) {
        Class sourceType = source instanceof Map ? Map.class : ClassUtils.getUserClass(source);
        Class targetType = target instanceof Map ? Map.class : ClassUtils.getUserClass(target);
        CacheKey key = createCacheKey(sourceType, targetType);
        if (autoCreate) {
            return CACHE.computeIfAbsent(key, k -> createCopier(sourceType, targetType));
        } else {
            return CACHE.get(key);
        }

    }

    /**
     * 创建属性拷贝代理类缓存
     * 
     * @param sourceType
     *            源类型
     * @param targetType
     *            目标类型
     * @return 缓存对象
     */
    private static CacheKey createCacheKey(Class sourceType, Class targetType) {
        return new CacheKey(sourceType, targetType);
    }

    /**
     * 创建属性拷贝代理类
     * 
     * @param sourceType
     *            源类型
     * @param targetType
     *            目标类型
     * @return 代理类
     */
    private static Copier createCopier(Class sourceType, Class targetType) {
        String sourceName = sourceType.getName();
        String targetName = targetType.getName();
        if (sourceName.startsWith(PACKAGE_START_STR)) {
            sourceName = sourceName.substring(PACKAGE_START_STR.length());
        }
        if (targetName.startsWith(PACKAGE_START_STR)) {
            targetName = targetName.substring(PACKAGE_START_STR.length());
        }
        String method = copyMethodCode(sourceType, targetType, sourceName, targetName);
        try {
            return Proxy.create(Copier.class).addMethod(method).newInstance();
        } catch (Exception e) {
            log.error("创建 copy 代理对象失败：\n{}", method, e);
            throw new UnsupportedOperationException(e.getMessage(), e);
        }
    }

    /**
     * 生成属性拷贝方法 code
     * 
     * @param sourceType
     *            源类型
     * @param targetType
     *            目标类型
     * @param sourceName
     *            源类型名
     * @param targetName
     *            目标类型名
     * @return 方法定义code
     */
    private static String copyMethodCode(Class sourceType, Class targetType, String sourceName, String targetName) {
        StringBuilder method = new StringBuilder("public void copy(Object s,Object t,java.util.Set ignore, ")
            .append("com.lianqi.emcpframework.core.bean.TypeConverter typeConverter ){").append("try{\n\t")
            .append(sourceName + " source = (" + sourceName + ") s;\n\t")
            .append(targetName + " target = (" + targetName + ") t;\n\t").append(copierCode(sourceType, targetType))
            .append("}catch(Exception e){\n\t").append("throw new RuntimeException(e.getMessage(),e);\n\t")
            .append("}\n\n").append("}");
        return method.toString();
    }

    /**
     * 生成拷贝代码
     * 
     * @param sourceType
     *            源类型
     * @param targetType
     *            目标类型
     * @return 拷贝代码
     */
    private static String copierCode(Class sourceType, Class targetType) {
        Map<String, Property> sourcePropertis = null;
        Map<String, Property> targetPropertis = null;

        if (Map.class.isAssignableFrom(sourceType)) {
            if (!Map.class.isAssignableFrom(targetType)) {
                targetPropertis = createProperty(targetType);
                sourcePropertis = createMapProperty(targetPropertis);
            }
        } else if (Map.class.isAssignableFrom(targetType)) {
            if (!Map.class.isAssignableFrom(sourceType)) {
                sourcePropertis = createProperty(sourceType);
                targetPropertis = createMapProperty(sourcePropertis);
            }
        } else {
            targetPropertis = createProperty(targetType);
            sourcePropertis = createProperty(sourceType);
        }
        if (sourcePropertis == null || targetPropertis == null) {
            throw new UnsupportedOperationException("不支持的类型 source:" + sourceType + " target:" + targetType);
        }
        return buildCode(targetType, sourcePropertis, targetPropertis);
    }

    /**
     * 创建map类型属性 属性名和Map类型属性定义的关系
     * 
     * @param propertyMap
     *            源属性映射关系
     * @return 目标属性映射关系
     */
    private static Map<String, Property> createMapProperty(Map<String, Property> propertyMap) {
        return propertyMap.values().stream().map(property -> new MapProperty(property.name))
            .collect(Collectors.toMap(Property::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
    }

    /**
     * 创建属性名和bean类型属性定义关系
     * 
     * @param clazz
     *            源类型
     * @return 目标属性映射关系
     */
    private static Map<String, Property> createProperty(Class clazz) {
        List<String> fieldNames =
            Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).collect(Collectors.toList());
        return Stream.of(propertyUtils.getPropertyDescriptors(clazz))
            .filter(property -> !"class".equals(property.getName()) && property.getReadMethod() != null
                && property.getWriteMethod() != null)
            .map(BeanProperty::new).sorted(Comparator.comparing(property -> fieldNames.indexOf(property.name)))
            .collect(Collectors.toMap(Property::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
    }

    private static String buildCode(Class targetType, Map<String, Property> sourcePropertis,
        Map<String, Property> targetPropertis) {
        StringBuilder code = new StringBuilder();
        for (Property sourceProp : sourcePropertis.values()) {
            Property targetProp = targetPropertis.get(sourceProp.getName());
            if (targetProp == null) {
                continue;
            }
            code.append("if(!ignore.contains(\"").append(sourceProp.getName()).append("\")){\n\t");
            if (!sourceProp.isPrimitive()) {
                code.append("if(source.").append(sourceProp.getReadMethod()).append("!=null){\n");
            }
            code.append(targetProp.generateVar(targetProp.getName())).append("=")
                .append(sourceProp.generateGetter(targetType, targetProp.getType())).append(";\n");
            if (!targetProp.isPrimitive()) {
                code.append("\tif(").append(sourceProp.getName()).append("!=null){\n");
            }
            code.append("\ttarget.").append(targetProp.generateSetter(targetProp.getType(), sourceProp.getName()))
                .append(";\n");
            if (!targetProp.isPrimitive()) {
                code.append("\t}\n");
            }
            if (!sourceProp.isPrimitive()) {
                code.append("\t}\n");
            }
            code.append("}\n");

        }
        return code.toString();
    }

    static class DefaultTypeConverter implements TypeConverter {
        private BeanCreator beanCreator = BEAN_CREATOR;

        @Override
        public <T> T convert(Object source, Class<T> targetClass, Class[] grnericType) {
            if (source == null) {
                return null;
            }
            if (targetClass == Object.class) {
                return (T)source;
            }
            if (source.getClass().isEnum()) {
                if (source instanceof EnumDict) {
                    Object val = ((EnumDict)source).getValue();
                    if (targetClass.isInstance(val)) {
                        return (T)val;
                    }
                    return convert(val, targetClass, grnericType);
                }
            }

            if (targetClass == String.class) {
                return convertToString(source);
            }

            if (targetClass == Date.class) {
                return convertToDate(source);
            }
            if (targetClass == Collection.class) {
                return convertToCollection(source, targetClass, grnericType);
            }
            if (targetClass.isEnum()) {
                return convertToEnum(source, targetClass, grnericType);
            }
            return convertToBean(source, targetClass);
        }

        public <T> T convertToBean(Object source, Class<T> targetClass) {
            try {
                org.apache.commons.beanutils.Converter converter =
                    BeanUtilsBean.getInstance().getConvertUtils().lookup(targetClass);
                if (null != converter) {
                    return converter.convert(targetClass, source);
                }
                return copy(source, beanCreator.newInstance(targetClass), this);
            } catch (Exception e) {
                log.warn("复制类型{} -> {}失败", source, targetClass);
                throw new UnsupportedOperationException(e.getMessage(), e);
            }
        }

        public <T> T convertToString(Object source) {
            if (source instanceof Date) {
                return (T)DateTimeUtils.format((Date)source, DateTimeUtils.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
            }
            return (T)String.valueOf(source);
        }

        public <T> T convertToEnum(Object source, Class<T> targetClass, Class[] grnericType) {
            if (EnumDict.class.isAssignableFrom(targetClass)) {
                Object val = EnumDict.find((Class)targetClass, String.valueOf(source)).orElse(null);
                if (targetClass.isInstance(val)) {
                    return (T)val;
                }
                return convert(val, targetClass, grnericType);
            }
            for (T t : targetClass.getEnumConstants()) {
                if (((Enum)t).name().equalsIgnoreCase(String.valueOf(source))) {
                    return t;
                }
            }
            log.warn("无法将：{}转为枚举：{}", source, targetClass);
            return null;

        }

        public <T> T convertToDate(Object source) {
            if (source instanceof String) {
                return (T)DateTimeUtils.formatUnknownString2Date((String)source);
            }
            if (source instanceof Number) {
                return (T)new Date(((Number)source).longValue());
            }
            if (source instanceof Date) {
                return (T)new Date(((Date)source).getTime());
            }
            return null;
        }

        public <T> T convertToCollection(Object source, Class<T> targetClass, Class[] grnericType) {

            Collection collection = newCollection(targetClass);
            Collection sourceCollection;
            if (source instanceof Collection) {
                sourceCollection = (Collection)source;
            } else if (source instanceof Object[]) {
                sourceCollection = Arrays.asList((Object[])source);
            } else {
                if (source instanceof String) {
                    String val = (String)source;
                    sourceCollection = Arrays.asList(val.split("[,]"));
                } else {
                    sourceCollection = Arrays.asList(source);
                }
            }
            if (grnericType != null && grnericType.length > 0 && grnericType[0] != Object.class) {
                for (Object object : sourceCollection) {
                    collection.add(convert(object, grnericType[0], null));
                }
            } else {
                collection.addAll(sourceCollection);
            }
            return (T)collection;
        }

        public <T> Collection newCollection(Class<T> targetClass) {
            if (targetClass == List.class) {
                return new ArrayList();
            } else if (targetClass == Set.class) {
                return new HashSet();
            } else if (targetClass == Queue.class) {
                return new LinkedList();
            } else {
                try {
                    return (Collection)targetClass.newInstance();
                } catch (Exception e) {
                    throw new UnsupportedOperationException("不支持的集合类型：" + targetClass, e);
                }
            }
        }

    }

}
